/*
* libtcod 1.5.1
* Copyright (c) 2008,2009,2010 Jice & Mingos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*     * Redistributions of source code must retain the above copyright
*       notice, this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * The name of Jice or Mingos may not be used to endorse or promote products
*       derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY JICE AND MINGOS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL JICE OR MINGOS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
 * This renderer is mostly copied and pasted from Antagonist's SkyFire GLSL roguelike engine
 */ 


#ifndef NO_OPENGL
#include "sys_opengl3.h"

#define GL3_PROTOTYPES 1
#include <GL3/gl3w.h>
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <glm/glm.hpp>
#include <glm/ext.hpp>

#include <GL3/DebugOutput.h>
#include <GL3/Shader.h>
#include <GL3/ShaderLoader.h>
#include <GL3/ShaderConstants.h>
#include <GL3/Uniform.h>
#include <GL3/Quad.h>

namespace GL3
{

	std::unique_ptr<Render::DebugOutput> Sys_Opengl3::gl_debug;
	std::unique_ptr<File::ShaderLoader> Sys_Opengl3::shader_loader;
	glm::mat4 Sys_Opengl3::orthoMatrix;
	/* console size (power of 2 and cells) */
	int Sys_Opengl3::POTconwidth, Sys_Opengl3::POTconheight, Sys_Opengl3::conwidth, Sys_Opengl3::conheight;
	/* font texture handle */
	unsigned int Sys_Opengl3::font_tex;
	/* font power of 2 size and pixels */
	int Sys_Opengl3::POTfontwidth,Sys_Opengl3::POTfontheight, Sys_Opengl3::fontwidth,Sys_Opengl3::fontheight;
	/* console data */
	unsigned int Sys_Opengl3::Tex[ConsoleDataEnumSize];
	unsigned char *Sys_Opengl3::data[ConsoleDataEnumSize];
	bool Sys_Opengl3::dirty[ConsoleDataEnumSize];
	Render::ShaderPtr Sys_Opengl3::m_shader;
	Render::UniformPtr Sys_Opengl3::m_uni_termsize;
	Render::UniformPtr Sys_Opengl3::m_uni_termcoef;
	Render::UniformPtr Sys_Opengl3::m_uni_fontw;
	Render::UniformPtr Sys_Opengl3::m_uni_fontcoef;
	Render::UniformPtr Sys_Opengl3::m_uni_font;
	Render::UniformPtr Sys_Opengl3::m_uni_term;
	Render::UniformPtr Sys_Opengl3::m_uni_termfcol;
	Render::UniformPtr Sys_Opengl3::m_uni_termbcol;
	Scene::QuadPtr Sys_Opengl3::m_quad;

/* called before creating window */
void Sys_Opengl3::TCOD_opengl_init_attributes() {
	static bool first=true;
	if ( first ) {
		SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
		SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
		SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
		SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32 );
		first=false;
	}
	/* ATI driver bug : enabling this might result in red screen */
	/*SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1); */
}
                                        
/* call after creating window */
bool Sys_Opengl3::TCOD_opengl_init_state(int conw, int conh, void *font) {
	SDL_Surface *font_surf=(SDL_Surface *)font;
	
	/* convert font for opengl */
	Uint32 rmask, gmask, bmask, amask;
	SDL_Surface *temp;
	SDL_Surface *temp_alpha;

	GLenum gl3wInitErr = gl3wInit();
	if(gl3wInitErr)
	{
		TCOD_LOG(("Failed to initialize OpenGL!\n"));
		return false;
	}
	if(gl3wIsSupported(3, 3) == false)
	{
		TCOD_LOG(("Opengl 3.3 is not supported!\n"));
		return false;
	}

	gl_debug = std::unique_ptr<Render::DebugOutput>(new Render::DebugOutput());
	shader_loader = std::unique_ptr<File::ShaderLoader>(new File::ShaderLoader(Kernel::getSingleton()->getResourceDir()+"Shaders/"));
	m_quad = std::make_shared<Scene::Quad>();
	
	/* set opengl state */
	glClearColor(1.0f, 1.0f, 0.0f, 0.0f);
	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

	orthoMatrix = glm::ortho<float>(0, conw, conh, 0.0f, -1.0f, 1.0f);

	if ( ! TCOD_ctx.fullscreen )
		wgl3wSwapIntervalEXT(0);

	/* compute pot size */
	conwidth=conw;
	conheight=conh;
	POTconwidth=POTconheight=1;
	while ( POTconwidth < conw ) POTconwidth *= 2;
	while ( POTconheight < conh ) POTconheight *= 2;


	#if SDL_BYTEORDER == SDL_BIG_ENDIAN
	rmask = 0xff000000;
	gmask = 0x00ff0000;
	bmask = 0x0000ff00;
	amask = 0x000000ff;
	#else
	rmask = 0x000000ff;
	gmask = 0x0000ff00;
	bmask = 0x00ff0000;
	amask = 0xff000000;
	#endif

	fontwidth=font_surf->w;
	fontheight=font_surf->h;
	POTfontwidth=POTfontheight=1;
	while ( POTfontwidth < fontwidth ) POTfontwidth *= 2;
	while ( POTfontheight < fontheight ) POTfontheight *= 2;

	SDL_SetColorKey(font_surf, SDL_SRCCOLORKEY, SDL_MapRGB(font_surf->format, 0, 0, 0));
	temp_alpha = SDL_DisplayFormatAlpha(font_surf);
	SDL_SetAlpha(temp_alpha, 0, SDL_ALPHA_TRANSPARENT);

	temp = SDL_CreateRGBSurface(SDL_SWSURFACE, POTfontwidth, POTfontheight, 32, bmask, gmask, rmask, amask); /*BGRA */

	SDL_BlitSurface(temp_alpha, NULL, temp, NULL);
	SDL_FreeSurface(temp_alpha);

	glGenTextures(1, &font_tex);
	glBindTexture(GL_TEXTURE_2D, font_tex);
	SDL_LockSurface(temp);

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, temp->w, temp->h, 0, GL_BGRA, GL_UNSIGNED_BYTE, temp->pixels);
	SDL_UnlockSurface(temp);
	SDL_FreeSurface(temp);
	return true;
}

bool Sys_Opengl3::TCOD_opengl_init_shaders() {
	int i;
	TCOD_color_t *fCol;

	/* Host side data init */
	for(i = 0; i< ConsoleDataEnumSize; i++)
	{
		data[i] = (unsigned char *)calloc(conwidth*conheight,ConsoleDataAlignment[i]);
		dirty[i]=true;
	}
	/* Initialize ForeCol to 255, 255, 255, 255 */
	fCol = (TCOD_color_t *)data[ForeCol];
	for( i = 0; i < conwidth*conheight; i++)
	{
	    fCol[i].r=255;
	    fCol[i].g=255;
	    fCol[i].b=255;
	}

    /* Generate Textures */
	glGenTextures(3, Tex);

	/* Character Texture */
	glBindTexture(GL_TEXTURE_2D, Tex[Character]);

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, POTconwidth, POTconheight, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);


    /* ForeCol Texture */
	glBindTexture(GL_TEXTURE_2D, Tex[ForeCol]);

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, POTconwidth, POTconheight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);

    /* BackCol Texture */
	glBindTexture(GL_TEXTURE_2D, Tex[BackCol]);

    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, POTconwidth, POTconheight, 0, GL_RGB, GL_UNSIGNED_BYTE, 0);

	glBindTexture(GL_TEXTURE_2D, 0);
	
	return true;
}

bool Sys_Opengl3::updateTex(ConsoleDataEnum dataType) {
	GLenum Type=0;
	glBindTexture(GL_TEXTURE_2D, Tex[dataType]);

	switch(ConsoleDataAlignment[dataType])
	{
	case 1:
		Type = GL_RED;
		break;
	/*case 2:
		Type = GL_RG;
		break; */
	case 3:
		Type = GL_RGB;
		break;
	case 4:
		Type = GL_RGBA;
	}
    /*glPixelStorei(GL_UNPACK_ALIGNMENT, 1); */
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, conwidth, conheight, Type, GL_UNSIGNED_BYTE, data[dataType]);

	glBindTexture(GL_TEXTURE_2D,0);
	return true;
}

void Sys_Opengl3::updateChar(ConsoleDataEnum dataType, int BufferPos, unsigned char *c, int length, int offset) {
	int i;
	dirty[dataType] = true;		/* Set dirty so Texture gets updated next frame */

	for(i = 0; i<length; i++) {
		data[dataType][BufferPos*ConsoleDataAlignment[dataType] + i + offset] = c[i];
	}

}

void Sys_Opengl3::TCOD_opengl_putchar_ex(int x, int y, unsigned char c, TCOD_color_t fore, TCOD_color_t back) {
	int loc = x+y*conwidth;

	if ( TCOD_ctx.renderer == TCOD_RENDERER_GLSL ) {
		updateChar(Character, loc, &c, ConsoleDataAlignment[Character], 0);
		updateChar(ForeCol, loc, &fore.r, ConsoleDataAlignment[ForeCol], 0);
	}
	updateChar(BackCol, loc, &back.r, ConsoleDataAlignment[BackCol], 0);

}

bool Sys_Opengl3::TCOD_opengl_render( int oldFade, bool *ascii_updated, void *console_buffer, void *prev_console_buffer) {
	int x,y,i;
	int fade = (int)TCOD_console_get_fade();
	bool track_changes=(oldFade == fade && prev_console_buffer);
	char_t *c=(char_t*)console_buffer;
	char_t *oc=(char_t*)prev_console_buffer;
	int ascii;
	/* update opengl data */
	/* TODO use function pointers so that libtcod's putchar directly updates opengl data */
	for (y=0;y<conheight;y++) {
		for (x=0; x<conwidth; x++) {
			bool changed=true;
			if ( c->cf == -1 ) c->cf = TCOD_ctx.ascii_to_tcod[c->c];
			if ( track_changes ) {
				changed=false;
				if ( c->dirt || ascii_updated[ c->c ] || c->back.r != oc->back.r || c->back.g != oc->back.g
					|| c->back.b != oc->back.b || c->fore.r != oc->fore.r
					|| c->fore.g != oc->fore.g || c->fore.b != oc->fore.b
					|| c->c != oc->c || c->cf != oc->cf) {
					changed=true;
				}
			}
			c->dirt=0;
			if ( changed ) {
				TCOD_opengl_putchar_ex(x,y,c->cf,c->fore,c->back);
			}
			c++;oc++;
		}
	}

	/* check if any of the textures have changed since they were last uploaded */
	for( i = 0; i< ConsoleDataEnumSize; i++)
	{
		if(dirty[i])
		{
			updateTex((ConsoleDataEnum)i);
			dirty[i] = false;
		}
	}
	
	/* actual rendering */
	m_shader->bind();
	
	/* Technically all these glUniform calls can be moved to SFConsole() when the shader is loaded */
	/* None of these change */
	/* The Textures still need to bind to the same # Activetexture throughout though */
	m_uni_termsize->bind(glm::vec2((float)conwidth, (float)conheight));
	m_uni_termcoef->bind(glm::vec2(1.0f/POTconwidth, 1.0f/POTconheight));
	m_uni_fontw->bind((float)TCOD_ctx.fontNbCharHoriz);
	m_uni_fontcoef->bind(glm::vec2((float)(fontwidth)/(POTfontwidth*TCOD_ctx.fontNbCharHoriz), (float)(fontheight)/(POTfontheight*TCOD_ctx.fontNbCharVertic)));
	
	glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, font_tex);
	m_uni_font->bind(0);
	
	glActiveTexture(GL_TEXTURE1);
	glBindTexture(GL_TEXTURE_2D, Tex[Character]);
	m_uni_term->bind(1);
	
	glActiveTexture(GL_TEXTURE2);
	glBindTexture(GL_TEXTURE_2D, Tex[ForeCol]);
	m_uni_termfcol->bind(2);
	
	glActiveTexture(GL_TEXTURE3);
	glBindTexture(GL_TEXTURE_2D, Tex[BackCol]);
	m_uni_termbcol->bind(3);
	
/*    DBGCHECKGL(shader->Validate()); */
	
	m_quad->render();
	
	glBindTexture(GL_TEXTURE_2D, 0);
	
	m_shader->unbind();

	/* fading overlay */
	/*if ( fade != 255 ) {
		int x=0,y=0;
		if ( TCOD_ctx.fullscreen ) {
			x=TCOD_ctx.fullscreen_offsetx/TCOD_ctx.font_width;
			y=TCOD_ctx.fullscreen_offsety/TCOD_ctx.font_height;
		}
		glBegin( GL_QUADS );
		glColor4f(TCOD_ctx.fading_color.r/255.0f,TCOD_ctx.fading_color.g/255.0f,TCOD_ctx.fading_color.b/255.0f,1.0f-fade/255.0f);
		glVertex2i( x, y);
		glVertex2i( x, y+conheight );
		glVertex2i( x+conwidth, y+conheight );
		glVertex2i( x+conwidth, y);
		glEnd();
	}*/
	return true;
}

void Sys_Opengl3::TCOD_opengl_swap() {
	SDL_GL_SwapBuffers();
}

void *Sys_Opengl3::TCOD_opengl_get_screen() {
	SDL_Surface *surf;
	int pixw,pixh,offx=0,offy=0,x,y;
	Uint32 mask,nmask;

	/* allocate a pixel buffer */
	pixw=TCOD_ctx.root->w * TCOD_ctx.font_width;
	pixh=TCOD_ctx.root->h * TCOD_ctx.font_height;
	surf=(SDL_Surface*)TCOD_sys_get_surface(pixw,pixh,false);
	if ( TCOD_ctx.fullscreen ) {
		offx=TCOD_ctx.fullscreen_offsetx;
		offy=TCOD_ctx.fullscreen_offsety;
	}

	/* get pixel data from opengl */
	//glPushClientAttrib( GL_CLIENT_PIXEL_STORE_BIT );
	glPixelStorei(GL_PACK_ROW_LENGTH, 0);
	glPixelStorei(GL_PACK_ALIGNMENT, 1);
	glReadPixels(offx,offy,pixw,pixh, GL_RGB, GL_UNSIGNED_BYTE, surf->pixels);
	//glPopClientAttrib();
	
	/* vertical flip (opengl has lower-left origin, SDL upper left) */
	mask=surf->format->Rmask|surf->format->Gmask|surf->format->Bmask;
	nmask=~mask;
	for (x=0; x < surf->w; x++) {
		for (y=0; y < surf->h/2; y++) {
			int offsrc=x*3+y*surf->pitch;
			int offdst=x*3+(surf->h-1-y)*surf->pitch;
			Uint32 *pixsrc = (Uint32 *)(((Uint8 *)surf->pixels)+offsrc);
			Uint32 *pixdst = (Uint32 *)(((Uint8 *)surf->pixels)+offdst);
			Uint32 tmp = *pixsrc;
			*pixsrc = ((*pixsrc) & nmask) | ((*pixdst) & mask);
			*pixdst = ((*pixdst) & nmask) | (tmp & mask);
		}
	}

	return (void *)surf;
}

void Sys_Opengl3::bind(const Render::ShaderPtr &shader)
{
	m_shader = shader;
}

void Sys_Opengl3::setUniforms(	const Render::UniformPtr &uni_termsize,
					const Render::UniformPtr &uni_termcoef,
					const Render::UniformPtr &uni_fontw,
					const Render::UniformPtr &uni_fontcoef,
					const Render::UniformPtr &uni_font,
					const Render::UniformPtr &uni_term,
					const Render::UniformPtr &uni_termfcol,
					const Render::UniformPtr &uni_termbcol)
{
	m_uni_termsize	= uni_termsize;
	m_uni_termcoef	= uni_termcoef;
	m_uni_fontw		= uni_fontw;
	m_uni_fontcoef	= uni_fontcoef;
	m_uni_font		= uni_font;
	m_uni_term		= uni_term;
	m_uni_termfcol	= uni_termfcol;
	m_uni_termbcol	= uni_termbcol;
}

#endif

}